/**
 * Copyright (c) 2007, Blue Hole Software. All rights reserved.
 * Code licensed under the Apache 2.0 License:
 * http://www.apache.org/licenses/
 */
package org.bhf.security.common;

import javax.security.auth.Subject;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;

/**
 * <code>ContextSubject</code> provides a means of associating
 * a <code>Subject</code> with the current thread of exection.
 */
public final class ContextSubject
{
    private static final ThreadLocal<Subject> localSubjects = new ThreadLocal<Subject>();

    /**
     * The <code>SUPER_USER</code> is used inside priveleged blocks to
     * bypass security checks.
     */
    public static final Subject SUPER_USER;

    static
    {
        SUPER_USER = new Subject();
        SUPER_USER.getPrincipals().add( new UserID( "" ) );
        SUPER_USER.getPublicCredentials( ).add( new FullName( "super user" ) );
        SUPER_USER.getPublicCredentials( ).add( new LoginID( "root" ) );
        SUPER_USER.getPublicCredentials( ).add( new Group( "root" ) );
        SUPER_USER.getPublicCredentials( ).add( new NickName( "root" ) );
    }

    /**
     * Associates the <code>SUPER_USER</code> with the current thread as a thread
     * local for the duration of the given action. If the thread already has
     * an associated <code>Subject</code>, this association will be
     * reestablished after the action cas been called.
     *
     * @param action The action to be performed.
     * @return The result of the action
     *
     */
    public static Object    doAsPrivileged( PrivilegedAction action )
    {
        return doAs( SUPER_USER, action );
    }

    /**
     * Associates a <code>SUPER_USER</code> with the current thread as a thread
     * local for the duration of the given action. If the thread already has
     * an associated <code>Subject</code>, this association will be
     * reestablished after the action cas been called.
     *
     * @param action The action to be performed.
     * @return The result of the action
     *
     * @throws java.security.PrivilegedActionException If the action throws an
     *      <code>Exception</code>.
     */
    public static Object    doAsPrivileged( PrivilegedExceptionAction action )
        throws PrivilegedActionException
    {
        return doAs( SUPER_USER, action );
    }

    /**
     * Associates a <code>Subject</code> with the current thread as a thread
     * local for the duration of the given action. If the thread already has
     * an associated <code>Subject</code>, this association will be
     * reestablished after the action cas been called.
     *
     * @param subject The <code>Subject</code> for the association. May be
     *      <code>null</code>.
     * @param action The action to be performed.
     * @return The result of the action
     *
     */
    public static Object    doAs( final Subject subject, final PrivilegedAction action )
    {
        Subject priorSubject = setSubject( subject );
        try
        {
            return action.run();
        }
        finally
        {
            setSubject( priorSubject );
        }
    }

    /**
     * Associates a <code>Subject</code> with the current thread as a thread
     * local for the duration of the given action. If the thread already has
     * an associated <code>Subject</code>, this association will be
     * reestablished after the action cas been called.
     *
     * @param subject The <code>Subject</code> for the association. May be
     *      <code>null</code>.
     * @param action The action to be performed.
     * @return The result of the action
     *
     * @throws java.security.PrivilegedActionException If the action throws an
     *      <code>Exception</code>.
     */
    public static Object    doAs( final Subject subject, final PrivilegedExceptionAction action )
        throws PrivilegedActionException
    {
        Subject priorSubject = setSubject( subject );
        try
        {
            return action.run();
        }
        catch( Exception e )
        {
            throw new PrivilegedActionException( e );
        }
        finally
        {
            setSubject( priorSubject );
        }
    }

    /**
     * Set the <code>Subject</code> associated with the current thread. This
     * method must be used with care to avoid allowing the association to
     * remaining when it is no longer valid. When ever possible, use the
     * <code>doAs()</code> methods instead.
     *
     * @param subject The <code>Subject</code> for the association. May be
     *      <code>null</code>.
     * @return The previous <code>Subject</code>, or <code>null</code> if none.
     */
    public static Subject   setSubject( final Subject subject )
    {
        final Subject priorSubject = localSubjects.get();
        localSubjects.set( subject );
        return priorSubject;
    }

    /**
     * Return the <code>Subject</code> currently associated with the current
     * thread or <code>null</code> if none.
     *
     * @return The <code>Subject</code> currently associated with the current
     * thread or <code>null</code> if none.
     */
    public static Subject   getContextSubject()
    {
        return localSubjects.get();
    }
}


